fix(generate-types): re-sync hardcoded TS output with contracts.ts + drift test#472
Closed
electrolobzik wants to merge 2 commits into
Closed
fix(generate-types): re-sync hardcoded TS output with contracts.ts + drift test#472electrolobzik wants to merge 2 commits into
electrolobzik wants to merge 2 commits into
Conversation
The TypeScript-as-Go-string literal in cmd/generate-types/main.go drifted from frontend/src/types/contracts.ts when PR smart-mcp-proxy#424 (Server Config tab parity) and PR smart-mcp-proxy#463 (per-tool enable/disable) edited contracts.ts directly without updating the generator. Running `go run ./cmd/generate-types` (invoked by Makefile's `frontend-build` target) silently reverts those fields, producing a dirty working tree on every `make build`: - Server.isolation_defaults - IsolationConfig.network_mode, IsolationConfig.extra_args - IsolationDefaults (entire interface) - Tool.disabled, Tool.approval_status The reverted contracts.ts also feeds back into Vite's bundle hashes, which is the likely reason web/frontend/dist/* also churns on rebuilds. This commit catches the generator up to the actual contracts.ts content. After this, `go run ./cmd/generate-types` is idempotent against HEAD. Verified: generator output is byte-identical to contracts.ts.
Adds TestContractsInSync, which runs the generator's content function and asserts byte-equality with the committed frontend/src/types/contracts.ts. The next time a contributor hand-edits contracts.ts (or hand-edits the hardcoded TS string in main.go) without updating both sides, CI fails with a clear message pointing at the fix: Either run \`go run ./cmd/generate-types\` from the module root (if the generator is the source of truth) or update the string literals in main.go (if contracts.ts is the source of truth). The drift this test guards against is what allowed PRs smart-mcp-proxy#424 and smart-mcp-proxy#463 to silently leave the generator out of sync. Refactors main.go to factor out generateFileContent() so the test can compare without re-implementing the header concat.
electrolobzik
added a commit
to HaloCollar/mcpproxy-go
that referenced
this pull request
May 16, 2026
Brings in the generator-sync + drift-prevention test: - fc0945c fix(generate-types): re-sync hardcoded TS output with contracts.ts - f2d90e5 test(generate-types): catch future contracts.ts drift in CI After this merge, running `go run ./cmd/generate-types` is idempotent against the committed frontend/src/types/contracts.ts and CI will fail on any future drift between the two.
Member
|
Thank you for this, @electrolobzik! 🙏 This was a real, recurring papercut — every One snag: the new byte-exact test failed on |
Dumbris
approved these changes
May 17, 2026
Dumbris
added a commit
that referenced
this pull request
May 17, 2026
…edes #472) (#475) * fix(generate-types): re-sync hardcoded TS output with contracts.ts The TypeScript-as-Go-string literal in cmd/generate-types/main.go drifted from frontend/src/types/contracts.ts when PR #424 (Server Config tab parity) and PR #463 (per-tool enable/disable) edited contracts.ts directly without updating the generator. Running `go run ./cmd/generate-types` (invoked by Makefile's `frontend-build` target) silently reverts those fields, producing a dirty working tree on every `make build`: - Server.isolation_defaults - IsolationConfig.network_mode, IsolationConfig.extra_args - IsolationDefaults (entire interface) - Tool.disabled, Tool.approval_status The reverted contracts.ts also feeds back into Vite's bundle hashes, which is the likely reason web/frontend/dist/* also churns on rebuilds. This commit catches the generator up to the actual contracts.ts content. After this, `go run ./cmd/generate-types` is idempotent against HEAD. Verified: generator output is byte-identical to contracts.ts. * test(generate-types): catch future contracts.ts drift in CI Adds TestContractsInSync, which runs the generator's content function and asserts byte-equality with the committed frontend/src/types/contracts.ts. The next time a contributor hand-edits contracts.ts (or hand-edits the hardcoded TS string in main.go) without updating both sides, CI fails with a clear message pointing at the fix: Either run \`go run ./cmd/generate-types\` from the module root (if the generator is the source of truth) or update the string literals in main.go (if contracts.ts is the source of truth). The drift this test guards against is what allowed PRs #424 and #463 to silently leave the generator out of sync. Refactors main.go to factor out generateFileContent() so the test can compare without re-implementing the header concat. * fix(generate-types): make TestContractsInSync CRLF-safe on Windows The new TestContractsInSync did a raw byte comparison of the committed contracts.ts against generator output. On Windows CI (core.autocrlf=true) git checks out contracts.ts with CRLF endings while the generator emits LF, so the test failed on windows-amd64 even though the contract was in sync (observed on PR #472). Two-layer fix: - .gitattributes pins frontend/src/types/contracts.ts to `text eol=lf` so it is checked out identically on every platform (the real fix). - The test now normalizes CRLF->LF before comparing, keeping it green regardless of a contributor's local git config (defense in depth). Verified: test passes with both LF and CRLF checkouts of contracts.ts; `go run ./cmd/generate-types` remains idempotent. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Roman Chernyak <electrolobzik@gmail.com> Co-authored-by: Claude Code <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The TypeScript-as-Go-string literal inside
cmd/generate-types/main.gohas drifted from the actualfrontend/src/types/contracts.tsit claims to generate. Runninggo run ./cmd/generate-types(invoked by theMakefile'sfrontend-buildtarget on everymake build/make build-server) silently reverts a chunk ofcontracts.ts, producing a dirty working tree after every build.The drift was introduced by two prior PRs that hand-edited
contracts.tswithout updating the generator:feat(webui): bring Server Config tab to parity with macOS tray— addedServer.isolation_defaults,IsolationConfig.network_mode,IsolationConfig.extra_args, and the entireIsolationDefaultsinterface.feat(webui): per-tool enable/disable + bulk Enable All/Disable All— addedTool.disabledandTool.approval_status.Neither PR touched
cmd/generate-types/main.go. The banner at the top ofcontracts.tssaysDO NOT EDIT - This file is auto-generated by cmd/generate-types, which made it natural to assume re-running the generator would just produce those fields. It doesn't — the generator's output is hand-maintained Go string literals, so manual edits tocontracts.tsneed a matching update to the generator or the next build silently reverts them.Changes
1. Sync the generator (
cmd/generate-types/main.go)Catches the hardcoded TS string literals up to the current
contracts.tscontent. Adds the six missing fields and theIsolationDefaultsinterface so the generator's output is byte-identical to the committedcontracts.ts.Also refactors
main()to extractgenerateFileContent()so the new test can validate the output without re-implementing the header concat.2. Drift-prevention test (
cmd/generate-types/main_test.go)TestContractsInSyncreads the committedfrontend/src/types/contracts.ts, computes whatcmd/generate-typeswould write today, and asserts byte-equality. The next time someone hand-edits one side without updating the other, CI fails with a clear message:This is the smallest discipline that would have caught both #424 and #463 before merge.
What this PR is not
This PR fixes the
contracts.tshalf of the dirty-tree problem only. The committedweb/frontend/dist/*bundle also churns on everymake build, but for an independent reason: the bundle inmainis older than the frontend source it claims to be built from (last refresh was in #433 wizard v2; subsequent feature PRs #424 / #463 added Vue code but didn't refresh the bundle), so Vite produces different content-hashed filenames than the ones committed. That deserves its own discussion (PLACEHOLDER + embedall:vs deterministic re-bundle vs leave-as-tracked-artifact) and a separate PR.Verification
Sanity-checked drift detection by appending one line to
contracts.tsand re-runninggo test ./cmd/generate-types/— test fails with the expected message, then passes again after reverting.Test plan
go test ./cmd/generate-types/passesgo run ./cmd/generate-typesis idempotent against committedcontracts.tsgo build ./...succeeds🤖 Generated with Claude Code